package com.flkin;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.extra.template.*;
import com.flkin.common.utils.StringUtils;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 模板构造器
 *
 * @author yanxiang.huang
 */
public class Generator {

    final static String DRIVER = "com.mysql.cj.jdbc.Driver";
    final static String PATTERN = "%";
    final static String COLUMN_NAME = "COLUMN_NAME";
    final static String TYPE_NAME = "TYPE_NAME";
    final static String REMARKS = "REMARKS";

    public static void main(String[] args) throws Exception {
        // 数据库信息
        String url = "jdbc:mysql://127.0.0.1:3306?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8";
        String user = "panda";
        String password = "1357";
        String database = "test";
        String table = "zoo";
        String prefix = "";
        // 模板信息
        String author = "yanxiang.huang";
        String pack = "com.flkin.modules.test";
        boolean overwrite = true;

        TableInfo tableInfo = getTableInfo(url, user, password, database, table);
        generate(tableInfo, prefix, author, pack, overwrite);
    }


    private static void generate(TableInfo tableInfo, String prefix, String author, String pack, boolean overwrite) throws IOException {
        Map<String, Object> genMap = genMap(tableInfo, prefix, author, pack);
        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH));
        String tableNoPrefix = tableInfo.name.substring(prefix.length());
        List<String> templates = getAdminTemplateNames();
        for (String templateName : templates) {
            String path = getAdminPath(tableNoPrefix, templateName, pack);
            File file = new File(path);
            if (file.exists() && !overwrite) {
                continue;
            }
            Template template = engine.getTemplate("generator/admin/" + templateName + ".ftl");
            genFile(file, template, genMap);
        }
    }

    private static void genFile(File file, Template template, Map<String, Object> map) throws IOException {
        Writer writer = null;
        try {
            FileUtil.touch(file);
            writer = new FileWriter(file);
            template.render(map, writer);
        } catch (TemplateException | IOException e) {
            throw new RuntimeException(e);
        } finally {
            assert writer != null;
            writer.close();
        }
    }

    /**
     * 获取后台模板生成路径
     *
     * @param table 表名
     * @param name  模板明
     * @param pack  包名
     * @return path
     */
    private static String getAdminPath(String table, String name, String pack) {
        String projectPath = System.getProperty("user.dir");
        String packagePath = projectPath + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator;
        String className = StringUtils.toCapitalizeCamelCase(table);
        if (StringUtils.isNotEmpty(pack)) {
            packagePath += pack.replace(".", File.separator) + File.separator;
        }
        if ("Entity".equals(name)) {
            return packagePath + "domain" + File.separator + className + ".java";
        }
        if ("Controller".equals(name)) {
            return packagePath + "controller" + File.separator + className + "Controller.java";
        }
        if ("Service".equals(name)) {
            return packagePath + "service" + File.separator + className + "Service.java";
        }
        if ("ServiceImpl".equals(name)) {
            return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
        }
        if ("Dto".equals(name)) {
            return packagePath + "service" + File.separator + "dto" + File.separator + className + "Dto.java";
        }
        if ("Mapper".equals(name)) {
            return packagePath + "service" + File.separator + "mapstruct" + File.separator + className + "Mapper.java";
        }
        if ("Repository".equals(name)) {
            return packagePath + "repository" + File.separator + className + "Repository.java";
        }
        return packagePath;
    }

    /**
     * 获取模板变量
     *
     * @param tableInfo 表信息
     * @param prefix    前缀
     * @param author    作者
     * @param pack      包路径
     * @return genMap
     */
    private static Map<String, Object> genMap(TableInfo tableInfo, String prefix, String author, String pack) {
        Map<String, Object> map = new HashMap<>();
        map.put("tableName", tableInfo.name);
        String tableNoPrefix = tableInfo.name.substring(prefix.length());
        map.put("className", StringUtils.toCapitalizeCamelCase(tableNoPrefix));
        map.put("changeClassName", StringUtils.toCamelCase(tableNoPrefix));
        map.put("tableName", tableInfo.name);
        map.put("author", author);
        map.put("date", DateUtil.now());
        map.put("hasDate", tableInfo.hasDate);
        map.put("package", pack);
        List<Map<String, Object>> columns = new ArrayList<>();
        for (ColumnInfo columnInfo : tableInfo.columns) {
            Map<String, Object> column = new HashMap<>();
            column.put("name", columnInfo.name);
            column.put("type", columnInfo.type);
            column.put("remark", columnInfo.remark);
            columns.add(column);
        }
        map.put("columns", columns);
        return map;
    }

    /**
     * 获取表信息
     *
     * @param url      库url
     * @param user     库用户
     * @param password 库密码
     * @param database 库
     * @param table    表
     * @return tableInfo
     * @throws Exception 数据异常
     */
    private static TableInfo getTableInfo(String url, String user, String password, String database, String table) throws Exception {
        Class.forName(DRIVER);
        Connection connection = DriverManager.getConnection(url, user, password);
        DatabaseMetaData metaData = connection.getMetaData();

        TableInfo tableInfo = new TableInfo();
        tableInfo.name = table;
        List<ColumnInfo> columns = new ArrayList<>();

        ResultSet rs = metaData.getColumns(database, PATTERN, table, PATTERN);
        while (rs.next()) {
            String name = rs.getString(COLUMN_NAME);
            String type = rs.getString(TYPE_NAME);
            String remark = rs.getString(REMARKS);
            ColumnInfo column = new ColumnInfo();
            column.name = StringUtils.toCamelCase(name);
            column.type = getJavaType(type);
            if (column.type.equals("Date")) {
                tableInfo.hasDate = true;
            }
            column.remark = remark;
            columns.add(column);
        }
        tableInfo.columns = columns;
        return tableInfo;
    }

    /**
     * 获取后台模板文件
     *
     * @return list
     */
    private static List<String> getAdminTemplateNames() {
        List<String> templateNames = new ArrayList<>();
        templateNames.add("Entity");
        templateNames.add("Dto");
        templateNames.add("Mapper");
        templateNames.add("Controller");
        templateNames.add("Service");
        templateNames.add("ServiceImpl");
        templateNames.add("Repository");
        return templateNames;
    }

    /**
     * 数据库类型映射到Java类型
     *
     * @param type 字段类型
     * @return javaType
     */
    private static String getJavaType(String type) {
        Map<String, String> mapping = new HashMap<>();
        mapping.put("tinyint", "Integer");
        mapping.put("int", "Integer");
        mapping.put("bigint", "Long");
        mapping.put("double", "Double");
        mapping.put("decimal", "Double");
        mapping.put("char", "String");
        mapping.put("varchar", "String");
        mapping.put("tinytext", "String");
        mapping.put("text", "String");
        mapping.put("mediumtext", "String");
        mapping.put("longtext", "String");
        mapping.put("date", "Date");
        mapping.put("datetime", "Date");
        mapping.put("timestamp", "Date");
        return mapping.get(type.toLowerCase());
    }

    private static class TableInfo {
        String name;
        boolean hasDate;
        List<ColumnInfo> columns;
    }

    private static class ColumnInfo {
        String name;
        String type;
        String remark;
    }
}
